/*
  pgfltstats.c - Page Fault Statistics System Call Implementation
  Ben Marks, Chris Lekas

  pgfltstats allows the user to determine information about the major
  and minor page faults of a PID, a group, or the entire system.
 */

/* Needed Includes from other parts of the kernel */
#include <linux/kernel.h>
#include <asm/uaccess.h>
#include <linux/syscalls.h>
#include <linux/sched.h>
#include <linux/list.h>
#include <linux/pgfltstats.h> // struct pf_info_struct definition

/* Flag values to choose between PID or UID information */
#define PGFLTSTAT_PROC 1
#define PGFLTSTAT_OWNER 2

/* Fill in information about multiple processes. Takes care
 of locking / unlocking the tasklist. */
void multiProcs (struct pf_info_struct* pf, uid_t uid);

/* Fill in information about all processes */
void allProcs (struct pf_info_struct* pf_info) ;
/* Fill in information about a subset of processes */
int someProcs (pid_t pid, int flag, struct pf_info_struct* pf) ; 

/*
  pgfltstats: Get information about page fault statistics, major
  and minor, for a PID, UID, or the system.

  Parameters:
  pid_t pid: Process ID to get page fault statistics about
  int flag: Either PGFLTSTAT_PROC or PGFLTSTAT_OWNER
            PGFLTSTAT_PROC - Return PF info about pid
	    PGFLTSTAT_OWNER - Return PF info about all processes owned
	                      by owner of PID.
  struct pf_info_struct info: A pointer to a struct pf_info_struct
            in user space to be filled in by the system call.

  Returns:
           0 => Success
     -EFAULT => Bad pointer / error copying from/to user space
     -EINVAL => Invalid Flag value or pid < -1
     -ESRCH  => PID does not exist

*/
SYSCALL_DEFINE3( pgfltstats, pid_t, pid, int, flag,
		 struct pf_info_struct __user *, info) {

  struct pf_info_struct pf_info;
  struct task_struct * ts;
  int retVal;
  
  /* Check that user gave us a good pointer */
  if ( ! access_ok( VERIFY_WRITE, info, sizeof( struct pf_info_struct ) ) ) {
    // Pointer address is invalid.
    return -EFAULT;
  }
  if ( copy_from_user( &pf_info, info, sizeof( struct pf_info_struct ) ) ) {
    // Can't copy from user-space.
    return -EFAULT;
  }

  /* Check if user wants information about all processes or just a
     subset of processes */
  if (pid >= 0) {
    // Some specific PID provided - get either UID or PID info
    if ( retVal = someProcs(pid, flag, &pf_info) ) {
      // Returns an error return value if failure
      return retVal; // Error finding pid, or invalid flag
    }
  }
  else if (pid == -1) {
    // User wants information about all processes
    allProcs(&pf_info) ;
  }
  else { // Invalid pid.
    return -EINVAL;
  }
  
  /* Copy the results back to user space */
  if ( copy_to_user( info, &pf_info, sizeof( struct pf_info_struct ) ) ) {
    // Can't copy back to user-space.
    return -EFAULT;
  }
  
  // All Successful!
  return 0;
}

/*
  A helper function for when the user wants to find informaiton about
  multiple processes (and thus the tasklist must be locked). This is 
  called when the user requested information either about the UID or 
  system wide page fault levels.

  Parameters:
  struct pf_info_struct* pf - Kernel level pf struct to fill in
  uid_t uid - User ID to gather information about, or -1 if system
              wide data are requested

  Returns nothing.
 */
void multiProcs (struct pf_info_struct* pf, uid_t uid) {
  struct task_struct *ts;

  // Initialize counters
  pf->minor = 0;
  pf->major = 0;
  pf->num_procs = 0;
  
  // Make sure the tasklist doesn't change under us
  read_lock(&tasklist_lock);
  
  // Iterate through all processes
  for_each_process(ts) {
    if ((ts->real_cred->uid == uid) || // This is a UID we're interested in 
	(uid < 0)) { // We're counting total page fault stats
      pf->minor += ts->min_flt;
      pf->major += ts->maj_flt;
      pf->num_procs++;
    }
  }
  
  // Done accessing tasklist
  read_unlock(&tasklist_lock);
}

/*
  Get information about all processes in the system. Really just a 
  wrapper around multiProcs to make main easier to understand
  and avoid duplicating the task list iteration code. This could
  even be a #define macro in the next version. 

  struct pf_info_struct* pf_info - Struct to fill in with system
    wide PF information.

 */
void allProcs (struct pf_info_struct* pf_info) {
  multiProcs(pf_info, -1);
}


/*
  Get information about either a PID or the owner of a PID. First
  determines the UID of the process and, if needed, calls multiprocs
  with the appropriate parameters to get information about the UID.
  Otherwise, just copies the information in the PID task struct
  without locking the entire tasklist. 

  Parameters:
  pid_t pid - PID that user wants information about
  int flag- Either PGFLTSTAT_PROC or PGFLTSTAT_OWNER
            PGFLTSTAT_PROC - Return PF info about pid
	    PGFLTSTAT_OWNER - Return PF info about all processes owned
	                      by owner of PID.
  struct pf_info_struct* pf - Kernel level struct to fill in with
    information about pfs. 
 */
int someProcs (pid_t pid, int flag, struct pf_info_struct* pf) {
  
  struct task_struct * ts;
  
  rcu_read_lock(); // Make sure task_struct doesn't change under us.
  ts = find_task_by_vpid( pid );
  if ( ! ts ) { // Error getting task_struct.
    rcu_read_unlock();
    return -ESRCH;
  }
  
  if (flag == PGFLTSTAT_PROC) {
    /* User only wants information about this process */
    pf->minor = ts->min_flt;
    pf->major = ts->maj_flt;
    pf->num_procs = 1;
  }
  else if (flag == PGFLTSTAT_OWNER) {
    /* User wants information about this Pi's group.  MultiProcs takes
     care of locking and unlocking the tasklist lock. */
    multiProcs(pf, ts->real_cred->uid);
  }
  else {
    // Flag is invalid;
    rcu_read_unlock();
    return -EINVAL;
  }

  // Unlock rcu_read_lock() before returning...
  rcu_read_unlock();
  return 0;
}
